﻿namespace Arms.Framework
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    public static class ObjectExtensions
    {
        #region Properties

        /// <summary>
        /// Get the expression property of this type
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertyName"></param>
        /// <param name="expression"></param>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static Expression GetExpression(this Type source, string propertyName, Expression expression, out PropertyInfo propertyInfo)
        {
            Type parentType = source;
            propertyInfo = null;
            
            // split the string on ',' to traverse the relationships and get the text of the final property
            string[] properties = propertyName.Split('.');
            
            foreach (string property in properties)
            {
                propertyInfo = parentType.GetProperty(property);
                expression = Expression.Property(expression, propertyInfo);
                parentType = propertyInfo.PropertyType;
            }
            
            return expression;
        }
        /// <summary>
        /// Gets the property's custom attributes of this object. Supports the use of "." for lookups
        /// </summary>
        /// <param name="source">The o.</param>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="inherit">if set to <c>true</c> [inherit].</param>
        /// <returns></returns>
        public static object[] GetPropertyCustomAttributes(this object source, string propertyName, bool inherit)
        {
            object currObject = null;
            object[] customAttribs = null;
            // split the string on '.' to traverse the relationships and get the text of the final property
            string[] properties = propertyName.Split('.');
            
            // loop through the properties
            for (int i = 0; i < properties.Count(); i++)
            {
                if (i == 0)
                {
                    if (source != null)
                    {
                        // if it is the first pass the property is of the object passed in
                        PropertyInfo pi = source.GetType().GetProperty(properties[i]);
                        if (pi != null)
                        {
                            //if we are drilling down further...
                            if (i < properties.Count() - 1)
                            {
                                //...retrieve the property...
                                currObject = pi.GetValue(source, null);
                            }
                            else
                            {
                                //...else retrieve the CustomAttributes
                                customAttribs = pi.GetCustomAttributes(false);
                            }
                        }
                    }
                }
                else
                {
                    if (currObject != null)
                    {
                        // the property belongs to the object retrieved previously
                        PropertyInfo pi = currObject.GetType().GetProperty(properties[i]);
                        if (pi != null)
                        {
                            //if we are drilling down further...
                            if (i < properties.Count() - 1)
                            {
                                //...retrieve the property...
                                currObject = pi.GetValue(source, null);
                            }
                            else
                            {
                                //...else retrieve the CustomAttributes
                                customAttribs = pi.GetCustomAttributes(false);
                            }
                        }
                    }
                }
            }
            
            return customAttribs ?? new object[0];
        }
        /// <summary>
        /// Gets the property value of this object. Supports use of "." for lookups
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        public static object GetPropertyValue(this object source, string propertyName)
        {
            object returnObject = null;
            
            // split the string on ',' to traverse the relationships and get the text of the final property
            string[] properties = propertyName.Split('.');
            
            // loop through the properties
            for (int i = 0; i < properties.Count(); i++)
            {
                if (i == 0)
                {
                    if (source != null)
                    {
                        // if it is the first pass the property is of the object passed in
                        PropertyInfo pi = source.GetType().GetProperty(properties[i]);
                        if (pi != null)
                        {
                            // retrieve the property
                            returnObject = pi.GetValue(source, null);
                        }
                    }
                }
                else
                {
                    if (returnObject != null)
                    {
                        // the property belongs to the object retrieved previously
                        PropertyInfo pi = returnObject.GetType().GetProperty(properties[i]);
                        if (pi != null)
                        {
                            // retrieve the property
                            returnObject = pi.GetValue(returnObject, null);
                        }
                    }
                }
            }
            
            return returnObject;
        }
        /// <summary>
        /// Gets the property value of this object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="propertyName"></param>
        /// <returns>Generic type</returns>
        public static T GetPropertyValue<T>(this object source, string propertyName)
        {
            var returnObject = source.GetPropertyValue(propertyName);
            
            return returnObject != null ? (T)Convert.ChangeType(returnObject, typeof(T)) : default(T);
        }
        /// <summary>
        /// Sets the property value of this object
        /// </summary>
        /// <param name="source"></param>
        /// <param name="propertyName"></param>
        /// <param name="value"></param>
        public static void SetPropertyValue(this object source, string propertyName, object value)
        {
            PropertyInfo pi = source.GetType().GetProperty(propertyName);
            if (pi != null)
            {
                pi.SetValue(source, value, null);
            }
        }
        /// <summary>
        /// True runtime type whenever the object is non-null.
        /// Defaults to the compile-time/defined type when the type is null.
        /// http://stackoverflow.com/questions/930147/c-sharp-get-type-of-null-object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static Type GetRunTimeType<T>(this T source)
        {
            return (source == null) ? typeof(T) : source.GetType();
        }

        #endregion
    }
}